/**
 * This file is part of Opintopaivakirja.
 *
 * Opintopaivakirja is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Opintopaivakirja is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Opintopaivakirja.  If not, see <http://www.gnu.org/licenses/>.
 */
package opintopaivakirja;

import java.sql.PreparedStatement;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;

/**
 * Luokka toteuttaa järjestelmässä tavoitteisiin, muistiinpanoihin ja
 * termeihin liittyvät ominaisuudet. Muistiinpano voidaan initialisoida
 * memoksi, tavoitteeksi tai termiksi.
 * 
 * @author Teemu Jonkkari
 * @version 1.0
 */
public class Muistiinpano {

    private int id;
    private int jarjestys;
    private int isa;
    private int tila;
    private int tyyppi;
    private String otsikko;
    private String asia;
    private ArrayList<Tagi> tagit;
    private ArrayList<Linkki> linkit;

    /**
     * Oletusmuodostin
     */
    public Muistiinpano() {
        this.id = 0;
        this.jarjestys = 0;
        this.isa = 0;
        this.tila = 0;
        this.linkit = new ArrayList<Linkki>();
        this.tagit = new ArrayList<Tagi>();
    }

    /**
     * Muodostin, jolla voidaan asettaa suoraan muistiinpanon tiedot.
     *<p>
     * Tila kertoo muistiinpanosta onko se 
     *<ul>
     * <li>Uusi (0)
     * <li>Vanha (1)
     * <li>Poistettava (-1)
     * </ul>
     * Tyyppi ilmoittaa muistiinpanon tarkemman tyypin
     * <ol>
     * <li>Tavoite</li>
     * <li>Muistiinpano</li>
     * <li>Termi</li>
     * </ol>
     * Jos muistiinpano kuuluu hierarkisesti jonkin toisen 
     * muistiinpanon alle, merkitään isa-muuttujaan hierarkiassa
     * yläpuolella olevan muistiinpanon id-tunniste.
     *
     * @param otsikko Muistiinpanon otsikko.
     * @param asia Muistiinpanon asiateksti.
     * @param tyyppi Muistiinpanon tyyppi (tavoite, muistiinpano, termi).
     * @param jarjestys Muistiinpanon järjestysnumero suhteessa muihin saman tason muistiinpanoihin.
     * @param isa Muistiinpanon isa-muistiinpanon id-tunniste (tulisi olla tavoite).
     */
    public Muistiinpano(String otsikko, String asia, int tyyppi,
            int jarjestys, int isa) {
        this.otsikko = otsikko;
        this.asia = asia;
        this.tyyppi = tyyppi;
        this.jarjestys = jarjestys;
        this.isa = isa;

        this.id = 0;
        this.tila = 0;
        this.linkit = new ArrayList<Linkki>();
        this.tagit = new ArrayList<Tagi>();
    }

    /**
     * Muodostin, jolla voidaan asettaa suoraan muistiinpanon kaikki tiedot.
     *<p>
     * Tarkoitettu tietokantakyselyn käytettäväksi, sillä tällöin
     * tiedetään myös id ja voidaan asettaa se.
     * Käyttäjän tekemällä uudella muistiinpanolla ei ole id:tä
     * ennen sen kantaan tallentamista.
     *
     * @param id Muistiinpanon id-tunniste.
     * @param otsikko Muistiinpanon otsikko.
     * @param asia Muistiinpanon asiateksti.
     * @param tyyppi Muistiinpanon tyyppi (tavoite, muistiinpano, termi).
     * @param jarjestys Muistiinpanon järjestysnumero suhteessa muihin saman tason muistiinpanoihin.
     * @param isa Muistiinpanon isa-muistiinpano, jonka tulisi olla tavoite.
     * @param tila Muistiinpanon tila (poistettava (-1), uusi (0), vanha (1), muokattu (2)).
     */
    protected Muistiinpano(int id, String otsikko, String asia,
            int tyyppi, int jarjestys, int isa, int tila) {
        this.id = id;
        this.otsikko = otsikko;
        this.asia = asia;
        this.tyyppi = tyyppi;
        this.jarjestys = jarjestys;
        this.isa = isa;
        this.tila = tila;

        this.linkit = new ArrayList<Linkki>();
        this.tagit = new ArrayList<Tagi>();
    }

    /**
     * Metodilla voidaan hakea projektin muistiinpanot tietokannasta.
     *
     * @param projektiid Projektin id-tunniste, joka omistaa muistiinpanot.
     * @return ArrayList, jossa löydetyt muistiinpanot.
     */
    public ArrayList<Muistiinpano> selectMuistiinpanot(int projektiid) {
        Connection conn = null;
        ResultSet rs = null;
        PreparedStatement ps = null;

        ArrayList<Muistiinpano> memot = new ArrayList<Muistiinpano>();

        try {
            conn = new Tietokanta().getYhteys();

            ps = conn.prepareStatement("" +
                    "SELECT M.ID, M.OTSIKKO, M.ASIA, M.TYYPPI, M.JARJESTYS, " +
                    "TM.TAVOITE_ID AS ISA " +
                    "FROM OPINTOPAIVAKIRJA.PROJEKTI P " +
                    "JOIN OPINTOPAIVAKIRJA.PROJEKTI_HAS_MUISTIINPANO PM " +
                    "JOIN OPINTOPAIVAKIRJA.MUISTIINPANO M " +
                    "LEFT JOIN OPINTOPAIVAKIRJA.TAVOITE_HAS_MUISTIINPANO TM " +
                    "ON M.ID = TM.MUISTIINPANO_ID " +
                    "ON PM.MUISTIINPANO_ID = M.ID " +
                    "ON P.ID = PM.PROJEKTI_ID " +
                    "WHERE P.ID = ? " +
                    "ORDER BY M.TYYPPI, M.JARJESTYS, M.ID, M.OTSIKKO ASC");
            ps.setInt(1, projektiid);

            rs = ps.executeQuery();

            while (rs.next()) {
                Muistiinpano m = null;

                if (rs.getInt("tyyppi") == 1) {
                    m = new Tavoite(rs.getInt("id"),
                            rs.getString("otsikko"),
                            rs.getString("asia"),
                            rs.getInt("tyyppi"),
                            rs.getInt("jarjestys"), 0, 1);
                } else if (rs.getInt("tyyppi") == 2) {
                    m = new Memo(rs.getInt("id"),
                            rs.getString("otsikko"),
                            rs.getString("asia"),
                            rs.getInt("tyyppi"),
                            rs.getInt("jarjestys"),
                            rs.getInt("isa"), 1);
                } else if (rs.getInt("tyyppi") > 2) {
                    m = new Termi(rs.getInt("id"),
                            rs.getString("otsikko"),
                            rs.getString("asia"),
                            rs.getInt("tyyppi"),
                            rs.getInt("jarjestys"), 0, 1);
                }

                if (m instanceof Memo) {
                    // muistiinpanon linkit
                    Linkki l = new Linkki();
                    m.linkit.addAll(l.selectLinkit(m.getId()));

                    // muistiinpanon tagit
                    Tagi t = new Tagi();
                    m.tagit.addAll(t.selectMuistiinpanonTagit(m.getId()));
                }

                // lisätään olio taulukkoon
                memot.add(m);

                // järjestetään muistiinpanot tavoite:memot -järjestykseen
                memot = this.jarjestaMuistiinpanot(memot);

            }

        } catch (SQLException ex) {
            Virheenkasittely.getInstance().setVirhe(Tietokanta.class.getName(), ex);
        } catch (NullPointerException e) {
            Virheenkasittely.getInstance().setVirhe(Tietokanta.class.getName(), e);
        } finally {
            if (ps != null) {
                try {
                    ps.close();
                    ps = null;
                } catch (SQLException ex) {
                    Virheenkasittely.getInstance().setVirhe(Tietokanta.class.getName(), ex);
                }
            }

            if (rs != null) {
                try {
                    rs.close();
                    rs = null;
                } catch (SQLException ex) {
                    Virheenkasittely.getInstance().setVirhe(Tietokanta.class.getName(), ex);
                }
            }
            return memot;
        }
    }

    /**
     * Metodilla voidaan lisätä muistiinpano tietokantaan.
     * 
     * @param kayttajaid Muistiinpanon omistavan käyttäjän id-tunniste.
     * @param projektiid Projektin id-tunniste, johon muistiinpano kuuluu.
     * @param muistiinpano Muistiinpano, joka lisätään tietokantaan.
     * @return boolean siitä, onnistuiko muistiinpanon lisääminen tietokantaan.
     */
    public boolean insertMuistiinpano(int kayttajaid,
            int projektiid, Muistiinpano muistiinpano) {
        //System.out.println("Muistiinpano.class insertMuistiipano(); ");
        Connection conn = null;
        ResultSet rs = null;
        PreparedStatement ps = null, ps2 = null;

        boolean vastaus = true;

        try {
            conn = new Tietokanta().getYhteys();

            ps = conn.prepareStatement("" +
                    "INSERT INTO OPINTOPAIVAKIRJA.MUISTIINPANO " +
                    "(OTSIKKO, ASIA, TYYPPI, JARJESTYS) VALUES (?, ?, ?, ?)");
            ps.setString(1, muistiinpano.otsikko);
            ps.setString(2, muistiinpano.asia);
            ps.setInt(3, muistiinpano.tyyppi); // tavoite 1, muistiinpano = 2, termi = 3
            ps.setInt(4, muistiinpano.jarjestys);
            ps.executeUpdate();

            if (muistiinpano.isa > 0) {
                ps = conn.prepareStatement("" +
                        "INSERT INTO OPINTOPAIVAKIRJA.TAVOITE_HAS_MUISTIINPANO " +
                        "(MUISTIINPANO_ID, TAVOITE_ID) " +
                        "VALUES (IDENTITY_VAL_LOCAL(), ?)");
                ps.setInt(1, muistiinpano.isa);
                ps.executeUpdate();
            }

            ps = conn.prepareStatement("" +
                    "INSERT INTO OPINTOPAIVAKIRJA.PROJEKTI_HAS_MUISTIINPANO " +
                    "(MUISTIINPANO_ID, PROJEKTI_ID) " +
                    "VALUES (IDENTITY_VAL_LOCAL(), ?)");
            ps.setInt(1, projektiid);

            ps.executeUpdate();

            // kysytään saatu id, tagien ja linkkien lisäämistä varten
            ps2 = conn.prepareStatement("" +
                    "SELECT IDENTITY_VAL_LOCAL() AS ID " +
                    "FROM OPINTOPAIVAKIRJA.MUISTIINPANO");
            rs = ps2.executeQuery();
            while (rs.next()) {
                muistiinpano.id = rs.getInt("ID");
            }

            // Tagit
            Tagi tag = new Tagi();
            tag.deleteTagit(muistiinpano.id);

            for (Tagi tagi : tagit) {
                tagi.insertTagi(muistiinpano.id, tagi);
            }

            // Linkit update (1), insert (0), delete (-1)
            for (Linkki linkki : muistiinpano.linkit) {
                if (linkki.getTila() < 0) {
                    linkki.deleteLinkki();
                } else if (linkki.getTila() == 0) {
                    linkki.insertLinkki(muistiinpano.id, linkki);
                } else if (linkki.getTila() > 0) {
                    linkki.updateLinkki();
                }
            }

        } catch (SQLException ex) {
            Virheenkasittely.getInstance().setVirhe(Tietokanta.class.getName(), ex);
            vastaus = false;
        } finally {
            if (ps != null) {
                try {
                    ps.close();
                    ps = null;
                } catch (SQLException ex) {
                    Virheenkasittely.getInstance().setVirhe(Tietokanta.class.getName(), ex);
                }
            }

            if (ps2 != null) {
                try {
                    ps2.close();
                    ps2 = null;
                } catch (SQLException ex) {
                    Virheenkasittely.getInstance().setVirhe(Tietokanta.class.getName(), ex);
                }
            }

            if (rs != null) {
                try {
                    rs.close();
                    rs = null;
                } catch (SQLException ex) {
                    Virheenkasittely.getInstance().setVirhe(Tietokanta.class.getName(), ex);
                }
            }
            return vastaus;
        }
    }

    /**
     * Metodilla voidaan päivittää muistiinpanon tiedot tietokantaan.
     *
     * @return boolean siitä, onnistuiko muistiinpanon paivittaminen tietokantaan.
     */
    public boolean updateMuistiinpano() {
        //System.out.println("Muistiinpano.class updateMuistiipano(); ");
        Connection conn = null;
        PreparedStatement ps = null;

        boolean vastaus = true;

        try {
            conn = new Tietokanta().getYhteys();

            ps = conn.prepareStatement("" +
                    "UPDATE OPINTOPAIVAKIRJA.MUISTIINPANO " +
                    "SET OTSIKKO = ?, ASIA = ?, TYYPPI = ?, " +
                    "JARJESTYS = ? WHERE ID = ?");

            ps.setString(1, this.otsikko);
            ps.setString(2, this.asia);
            ps.setInt(3, this.tyyppi);
            ps.setInt(4, this.jarjestys);
            ps.setInt(5, this.id);

            ps.executeUpdate();

            if (this.isa > 0) {
                this.liitaMuistiinpanoon(this.isa);
            }

            // Linkit update (1), insert (0) ja delete (-1)
            for (Linkki linkki : linkit) {
                if (linkki.getTila() < 0) {
                    linkki.deleteLinkki();
                } else if (linkki.getTila() == 0) {
                    linkki.insertLinkki(this.id, linkki);
                } else if (linkki.getTila() > 0) {
                    linkki.updateLinkki();
                }
            }

            // Tagit
            Tagi tag = new Tagi();
            tag.deleteTagit(this.id);

            for (Tagi tagi : tagit) {
                tagi.insertTagi(this.id, tagi);
            }

        } catch (SQLException ex) {
            Virheenkasittely.getInstance().setVirhe(Tietokanta.class.getName(), ex);
            vastaus = false;
        } finally {
            if (ps != null) {
                try {
                    ps.close();
                    ps = null;
                } catch (SQLException ex) {
                    Virheenkasittely.getInstance().setVirhe(Tietokanta.class.getName(), ex);
                }
            }
            return vastaus;
        }
    }

    /**
     * Metodilla voidaan poistaa muistiinpano tietokannasta.
     *
     * @return boolean siitä, onnistuiko muistiinpanon poistaminen tietokannasta
     */
    public boolean deleteMuistiinpano() {
        //System.out.println("Muistiinpano.class deleteMuistiipano(); ");
        Connection conn = null;
        PreparedStatement ps = null;

        boolean vastaus = true;

        try {
            conn = new Tietokanta().getYhteys();

            ps = conn.prepareStatement("" +
                    "DELETE FROM OPINTOPAIVAKIRJA.MUISTIINPANO WHERE ID = ?");
            ps.setInt(1, this.id);

            ps.executeUpdate();

        } catch (SQLException ex) {
            Virheenkasittely.getInstance().setVirhe(Tietokanta.class.getName(), ex);
            vastaus = false;
        } finally {
            if (ps != null) {
                try {
                    ps.close();
                    ps = null;
                } catch (SQLException ex) {
                    Virheenkasittely.getInstance().setVirhe(Tietokanta.class.getName(), ex);
                }
            }
            return vastaus;
        }
    }

    /**
     * Metodilla voidaan liittää muistiinpano tavoitteeseen.
     *
     * @return boolean siitä, onnistuiko muistiinpanon liittäminen tavoitteeseen.
     */
    private boolean liitaMuistiinpanoon(int isaid) {
        Connection conn = null;
        ResultSet rs = null;
        PreparedStatement ps = null, ps2 = null;

        boolean vastaus = true;

        try {
            conn = new Tietokanta().getYhteys();

            if (this.tila > 0) {
                ps2 = conn.prepareStatement("" +
                        "SELECT COUNT(*) AS LKM " +
                        "FROM OPINTOPAIVAKIRJA.TAVOITE_HAS_MUISTIINPANO " +
                        "WHERE MUISTIINPANO_ID = ? AND TAVOITE_ID = ?");
                ps2.setInt(1, this.id);
                ps2.setInt(2, isaid);
                rs = ps.executeQuery();

                int lkm = 1;

                while (rs.next()) {
                    lkm = rs.getInt("lkm");
                }

                if (lkm < 1) {
                    ps = conn.prepareStatement("" +
                            "INSERT INTO " +
                            "OPINTOPAIVAKIRJA.TAVOITE_HAS_MUISTIINPANO " +
                            "(MUISTIINPANO_ID, TAVOITE_ID) VALUES (?, ?)");
                    ps.setInt(1, this.id);
                    ps.setInt(2, isaid);
                }

            } else {
                ps = conn.prepareStatement("INSERT INTO " +
                        "OPINTOPAIVAKIRJA.TAVOITE_HAS_MUISTIINPANO " +
                        "(MUISTIINPANO_ID, TAVOITE_ID) " +
                        "VALUES (IDENTITY_VAL_LOCAL(), ?)");
                ps.setInt(1, isaid);
            }

            ps.executeUpdate();

        } catch (SQLException ex) {
            // Virheen nro 30000 ja State 23505 Duplikaattiyritys
            // if (ex.getErrorCode() != 30000 && ex.getSQLState().equals("23505")) {
            Virheenkasittely.getInstance().setVirhe(Tietokanta.class.getName(), ex);
            vastaus = false;
        } finally {
            if (ps != null) {
                try {
                    ps.close();
                    ps = null;
                } catch (SQLException ex) {
                    Virheenkasittely.getInstance().setVirhe(Tietokanta.class.getName(), ex);
                }
            }

            if (ps2 != null) {
                try {
                    ps2.close();
                    ps2 = null;
                } catch (SQLException ex) {
                    Virheenkasittely.getInstance().setVirhe(Tietokanta.class.getName(), ex);
                }
            }

            if (rs != null) {
                try {
                    rs.close();
                    rs = null;
                } catch (SQLException ex) {
                    Virheenkasittely.getInstance().setVirhe(Tietokanta.class.getName(), ex);
                }
            }

            return vastaus;
        }
    }

    /**
     * Metodilla voidaan järjestää muistiinpanot tavoite->muistiinpanot järjestykseen.
     * 
     * @param memot ArrayList, jossa on jarjesteltävät Muistiinpanot.
     * @return ArrayList, joka on järjestetty.
     */
    private ArrayList<Muistiinpano> jarjestaMuistiinpanot(ArrayList<Muistiinpano> memot) {
        ArrayList<Muistiinpano> jmemot = new ArrayList<Muistiinpano>();
        ArrayList<Muistiinpano> tavoitteet = new ArrayList<Muistiinpano>();
        ArrayList<Muistiinpano> muistiinpanot = new ArrayList<Muistiinpano>();

        // tässä ei nyt oteta huomioon muistiinpanojen järjestysnumeroita

        // lajitellaan kaikki kahteen taulukkoon: tavoitteet ja memot
        for (Muistiinpano muistiinpano : memot) {
            if (muistiinpano instanceof Tavoite) {
                tavoitteet.add(muistiinpano);
            } else if (muistiinpano instanceof Memo) {
                muistiinpanot.add(muistiinpano);
            }
        }

        // lisätään tavoite ja sen jälkeen sen muistiinpanot
        for (Muistiinpano tavoite : tavoitteet) {
            jmemot.add(tavoite);

            for (Muistiinpano memo : muistiinpanot) {
                if (tavoite.getId() == memo.getIsa()) {
                    jmemot.add(memo);
                }
            }
        }
        return jmemot;
    }

    /**
     * @return the asia
     */
    public String getAsia() {
        return asia;
    }

    /**
     * @param asia the asia to set
     */
    public void setAsia(String asia) {
        this.asia = asia;
    }

    /**
     * @return the id
     */
    public int getId() {
        return id;
    }

    /**
     * @param id the id to set
     */
    public void setId(int id) {
        this.id = id;
    }

    /**
     * @return the tagit
     */
    public ArrayList<Tagi> getTagit() {
        return tagit;
    }

    /**
     * @param tagit the tagit to set
     */
    public void setTagit(ArrayList<Tagi> tagit) {
        this.tagit = tagit;
    }

    /**
     * @return the jarjestys
     */
    public int getJarjestys() {
        return jarjestys;
    }

    /**
     * @param jarjestys the jarjestys to set
     */
    public void setJarjestys(int jarjestys) {
        this.jarjestys = jarjestys;
    }

    /**
     * @return the linkit
     */
    public ArrayList<Linkki> getLinkit() {
        return linkit;
    }

    /**
     * @param linkit the linkit to set
     */
    public void setLinkit(ArrayList<Linkki> linkit) {
        this.linkit = linkit;
    }

    /**
     * @return the otsikko
     */
    public String getOtsikko() {
        return otsikko;
    }

    /**
     * @param otsikko the otsikko to set
     */
    public void setOtsikko(String otsikko) {
        this.otsikko = otsikko;
    }

    /**
     * @return the tyyppi
     */
    public int getTyyppi() {
        return tyyppi;
    }

    /**
     * @param tyyppi the tyyppi to set
     */
    public void setTyyppi(int tyyppi) {
        this.tyyppi = tyyppi;
    }

    /**
     * @return the isa
     */
    public int getIsa() {
        return isa;
    }

    /**
     * @param isa the isa to set
     */
    public void setIsa(int isa) {
        this.isa = isa;
    }

    /**
     * @return the tila
     */
    public int getTila() {
        return tila;
    }

    /**
     * @param tila the tila to set
     */
    public void setTila(int tila) {
        this.tila = tila;
    }

    /**
     * @param linkki the linkki to set
     */
    public void setLinkki(Linkki linkki) {
        this.linkit.add(linkki);
    }
}

